# -*- coding:utf-8 -*-
__author__ = 'xigu007'
__all__ = ('RelativeComposite', 'Composite', 'Division', 'Content', 'Group', 'Context','WithUidBase',)
from kivy.event import ObjectWithUid
from kivy.clock import Clock
from kivy.properties import BooleanProperty,NumericProperty,OptionProperty
from kivy.graphics.transformation import Matrix
from kivy.uix.widget import WidgetBase, WidgetMetaclass, Widget
from kivy.lang import Builder
from kivy.graphics import Color, InstructionGroup
from kivy.context import get_current_context
from time import time
from kivy.weakproxy import WeakProxy
from transform import Transform
from composite_widget.animate import animate_factory

WithUidBase = WidgetMetaclass('WithUidBase', (ObjectWithUid, ), {})


class Content(WithUidBase):
    def __init__(self, **kwargs):
        self._x = 0
        self._y = 0
        self._size=(0,0)

        #内容大小占parent(div对象)大小的比例,如果为none,就是内容自身大小
        self._size_hint = None

        #内容位置,只有parent(div对象)为float布局是有效,其它布局无效.
        self._pos_hint = None
        self._center_hint = None

        #内容对象停靠位置,能停靠在左边,右边和中间, 不需要上下停靠.
        self._anchor = 0

        #如果内容的parent(div对象)是horizontal,内容之间对齐有效, 内容只能是行排版
        #内容之间对齐方式,有上边对齐,中间对齐和下边对齐.没有左右对齐.
        #如果内容的parent(div对象)是vertical,内容之间对齐无效,只能中间对齐,且垂直排版
        self._align = 0

        #内容对象之间的左右上下间距，内容在DIV中占位大小为可显示区域大小加上margin
        self._margin = (0,0,0,0)

        #内容（实际占位大小）的最大和最小值,如果超过内容最大值就按最大值，同理最小值
        self._max_width = None
        self._max_height = None
        self._min_width = None
        self._min_height = None

        #属性已经更改
        self.property_changed =True

        #对内容进行矩阵变换。主要包括透明，缩放，旋转（3d）平移
        self._transform = None

        #对内容进行动画，可动画属性也是透明，缩放，旋转（3d）平移
        self._animate = None
        #动画参数
        self._animate_param = None

        #是否enable touch
        self._touch_enable = False
        #触摸事件回调， 回调函数参数为:(content，action)  action: 'down', 'up', 'release'
        self._touch_callback = None
        self._touch_time = None
        self._touch_state = 'normal' # 'down','normal'


        self._ref_size = None
        #graphic instruction
        self._vt_instruction = None
        self._color_instruction = None

        self._color = (1,1,1,1)

        #内容所在的div是否wrap_height
        self._be_wrap = False

        #是否已经添加进compositewidget的canvas
        self._added_flg = False

        #如果设置True,在vt_instruction加入canvas前加入Color(1,1,1,1), 这样避免每次都加入Color(1,1,1,1)
        self._clear_color_state = False

        self.parent = None
        self._proxy_ref = None
        self.id = ''
        self.cls = []
        self._instruction_group = InstructionGroup(nocompiler=True)
        super(Content, self).__init__(**kwargs)

    @property
    def __self__(self):
        return self

    @property
    def proxy_ref(self):
        _proxy_ref = self._proxy_ref
        if _proxy_ref is not None:
            return _proxy_ref
        self._proxy_ref = _proxy_ref = WeakProxy(self)
        return _proxy_ref

    def collide_point(self, x, y):
        width, height = self._size
        return self._x <= x <= (self._x + width) and self._y <= y <= (self._y + height)

    def on_touch_down(self, touch):
        if self._touch_enable:
            if touch.is_mouse_scrolling:
                return False
            if not self.collide_point(touch.x, touch.y):
                return False
            if self in touch.ud:
                return False
            touch.ud[self] = True
            self._touch_time = time()
            self._touch_state ='down'
            self._touch_callback(self, 'down')
            return True
        return False

    def on_touch_up(self, touch):
        if self._touch_enable:
            if self._touch_state == 'down':
                self._touch_callback(self, 'up')
            self._touch_state = 'normal'
            if not self.collide_point(*touch.pos):
                return
            touchtime = time() - self._touch_time
            if touchtime > 0.035:
                self._touch_callback(self, 'release')

    def do_apply(self):
        '''显示内容，把大小和位置设置到顶点指令上
        :return:
        '''
        if self._vt_instruction:
            self._vt_instruction.size = (self._size[0] - self._margin[0] - self._margin[1], self._size[1] - self._margin[2] - self._margin[3])
            self._vt_instruction.pos = (self._x + self._margin[0], self._y + self._margin[3])

    def push_state(self, instruction_group):
        transform = self._transform
        if transform is not None:
            return transform.push_transform_state(instruction_group)
        return self._added_flg


    def pop_state(self,instruction_group):
        transform = self._transform
        if transform is not None:
            transform.pop_transform_state(instruction_group)

    def add_instruction(self):
        if self.parent is None:
            return
        instruction_group = self._instruction_group
        added_flg = self.push_state(instruction_group)
        if not added_flg:
            if self._vt_instruction:
                if self._clear_color_state:
                    self._color_instruction = Color(*self._color)
                    instruction_group.add(self._color_instruction)
                instruction_group.add(self._vt_instruction)
        self.pop_state(instruction_group)
        if not added_flg:
            if self._vt_instruction:
                if self._clear_color_state:
                    self._instruction_group.add(Color(1,1,1,1))
                self._added_flg = True

    def notify_property_changed(self, immediate=False):
        '''内容属性变化后通知父类
        :return:
        '''
        self.property_changed = True
        if self.parent:
            self.parent.notify_changed(immediate)

    def _is_relayout(self, ref_size):
        #content 是否重新layout，如果ref_size[0]变化就重刷，如果self._be_wrap = True，ref_size[1]变化不会引起重刷
        self_ref_size = self._ref_size
        if self_ref_size is None:
            self._ref_size = ref_size[:]
            return True
        elif (self_ref_size[0] != ref_size[0]) or  (not self._be_wrap and self_ref_size[1] != ref_size[1]):
            self._be_wrap = True if ref_size[1] == 0 else False
            self._ref_size = ref_size[:]
            return True
        return False

    def layout_size(self, ref_size):
        '''内容大小
        :param ref_size:
        :return:
        '''
        return self._size

    def layout_pos(self,x, y):
        self._x = x
        self._y = y

    def set_transform(self, **kwargs):
        #变换参数字典类型，格式{'opacity':1.0, 'scale':{'origin':0,'x':1.0,'y':1.0, 'z':0}, 'rotate':{'angle':0,'origin':0, 'axis':(0,0,1)}\
        # 'translate' :{'x':0, 'y':0, 'z':0}}
        if self._transform is None:
            self._transform = Transform(self)
        self._transform.set_parameter(**kwargs)

    @property
    def pos(self):
        return (self._x, self._y)
    @property
    def size(self):
        return self._size

    @property
    def clear_color_state(self):
        return self._clear_color_state

    @clear_color_state.setter
    def clear_color_state(self, value):
        self._clear_color_state = value

    @property
    def size_hint(self):
        return self._size_hint
    @size_hint.setter
    def size_hint(self, value):
        if self._size_hint != value:
            self.notify_property_changed()
            self._size_hint = value

    @property
    def size_hint_x(self):
        if self._size_hint is not None:
            return self._size_hint[0]
        return 0

    @size_hint_x.setter
    def size_hint_x(self, value):
        if self._size_hint is not None:
            if self._size_hint[0] != value:
                self._size_hint[0] = value
                self.notify_property_changed()
        else:
            self._size_hint = (value, None)
            self.notify_property_changed()

    @property
    def size_hint_y(self):
        if self._size_hint is not None:
            return self._size_hint[1]
        return 0

    @size_hint_y.setter
    def size_hint_y(self, value):
        if self._size_hint is not None:
            if self._size_hint[1] != value:
                self._size_hint[1] = value
                self.notify_property_changed()
        else:
            self._size_hint = (None, value)
            self.notify_property_changed()

    @property
    def max_width(self):
        return self._max_width

    @max_width.setter
    def max_width(self, value):
        if self._max_width != value:
            self._max_width = value
            #print 'max_width changed size[0]={} maxwidth={}'.format(self._size[0], value)
            if self.parent:
                if self._size[0] - self.margin[0] - self.margin[1] > self._max_width:
                    host_div = self.parent.get_host_division()
                    if host_div is not None:
                        self.notify_property_changed()

    @property
    def max_height(self):
        return self._max_height

    @max_height.setter
    def max_height(self, value):
        if self._max_height != value:
            self._max_height = value
            if self.parent:
                if self._size[1] - self.margin[2] - self.margin[3] > self._max_height:
                    host_div = self.parent.get_host_division()
                    if host_div is not None:
                        self.notify_property_changed()

    @property
    def min_width(self):
        return self._min_width

    @min_width.setter
    def min_width(self, value):
        if self._min_width != value:
            self._min_width = value
            if self.parent:
                if self._size[0] - self.margin[0] - self.margin[1] < self._min_width:
                    host_div = self.parent.get_host_division()
                    if host_div is not None:
                        self.notify_property_changed()

    @property
    def min_height(self):
        return self._min_height

    @min_height.setter
    def min_height(self, value):
        if self._min_height != value:
            self._min_height = value
            if self.parent:
                if self._size[1]  - self.margin[2] - self.margin[3] < self._min_height:
                    host_div = self.parent.get_host_division()
                    if host_div is not None:
                        self.notify_property_changed()
    @property
    def center_hint(self):
        return self._center_hint

    @center_hint.setter
    def center_hint(self, value):
        if self._center_hint != value:
            self._center_hint = value
            if self.parent:
                host_div = self.parent.get_host_division()
                if host_div is not None:
                    if host_div.contents_typesetting == 'float':
                        self.notify_property_changed()
    @property
    def pos_hint(self):
        return self._pos_hint

    @pos_hint.setter
    def pos_hint(self, value):
        if self._pos_hint != value:
            self._pos_hint = value
            if self.parent:
                host_div = self.parent.get_host_division()
                if host_div is not None:
                    if host_div.contents_typesetting == 'float':
                        self.notify_property_changed()

    @property
    def anchor(self):
        return self._anchor

    @anchor.setter
    def anchor(self, value):
        if value == 'center':
            anchor = 0
        elif value == 'left':
            anchor = 1
        elif value == 'right':
            anchor = 2
        else:
            anchor = 0
        if self._anchor != anchor:
            self._anchor = anchor
            if self.parent:
                host_div = self.parent.get_host_division()
                if host_div is not None:
                    if host_div.contents_typesetting == 'anchor':
                        self.notify_property_changed()
    @property
    def align(self):
        return self._align

    @align.setter
    def align(self, value):
        if value == 'middle':
            align = 0
        elif value == 'bottom':
            align = 1
        elif value == 'top':
            align = 2
        if self._align != align:
            self._align = align
            if self.parent:
                host_div = self.parent.get_host_division()
                if host_div is not None:
                    if host_div.contents_typesetting == 'anchor':
                        self.notify_property_changed()

    @property
    def margin(self):
        return self._margin

    @margin.setter
    def margin(self, value):
        if self._margin != value:
            self._margin = value
            self.notify_property_changed()

    @property
    def touch_callback(self):
        return self._touch_callback

    @touch_callback.setter
    def touch_callback(self, callback):
        if callback is None:
            self._touch_enable = False
            return
        if not callable(callback):
            raise Exception('callback is not callable')
        self._touch_enable = True
        self._touch_callback = callback

    @property
    def transform(self):
        return self._transform

    @transform.setter
    def transform(self, value):
        self.set_transform(**value)

    #动画基础属性透明，旋转，缩放，平移
    @property
    def opacity(self):
        pass
    @opacity.setter
    def opacity(self, value):
        if self._transform:
            self._transform.set_opacity(value)

    @property
    def scale(self):
        pass
    @scale.setter
    def scale(self, value):
        if self._transform:
            self._transform.set_scale(*value)

    @property
    def scale_origin(self):
        pass
    @scale_origin.setter
    def scale_origin(self, value):
        if self._transform:
            self._transform.set_scale_origin(value)

    @property
    def translate(self):
        pass
    @translate.setter
    def translate(self, value):
        if self._transform:
            self._transform.set_translate(*value)
    @property
    def rotate_angle(self):
        pass
    @rotate_angle.setter
    def rotate_angle(self, value):
        if self._transform:
            self._transform.set_rotate_angle(value)

    @property
    def rotate_origin(self):
        pass
    @rotate_origin.setter
    def rotate_origin(self, value):
        if self._transform:
            self._transform.set_rotate_origin(value)

    @property
    def rotate_axis(self):
        pass
    @rotate_axis.setter
    def rotate_axis(self, value):
        if self._transform:
            self._transform.set_rotate_axis(value)

def _layout_horizontal_content(direct, prev_x, ref_y, ref_height, content_size, align):
    if 1 == direct:
        content_x = prev_x
        next = content_x + content_size[0]
    elif 2 == direct:
        content_x = prev_x  - content_size[0]
        next = content_x
    if 0 == align: #居中对齐
        content_y = ref_y + 0.5*(ref_height - content_size[1])
    elif 1 == align: #下对齐
        content_y = ref_y
    elif 2 == align: #上对齐
        content_y = ref_y + ref_height - content_size[1]
    return (next, content_x, content_y)

def _anchor_contents_layout(x, y, size, padding, contents_children):
    #内容对象按自己的锚定方式来排版
    div_size = size
    left_x = x + padding[0]
    right_x = x + size[0] - padding[1]
    if contents_children:
        #第一个内容对象决定停靠在div的哪个高度
        ref_y = y
        first = contents_children[0]
        ref_h = first.layout_size(div_size)[1]
        align = first.align
        if 0 == align:
            ref_y = y + 0.5*(div_size[1] - ref_h)
        elif 1 == align:
           ref_y += padding[3]
        elif 2 == align:
            ref_y = y + div_size[1] - ref_h - padding[2]
        center_children = []
        for child in contents_children:
            if 1 == child.anchor: #锚定左边
                content_size = child.layout_size(div_size)
                left_x, pos_x, pos_y = _layout_horizontal_content(1,left_x, ref_y, ref_h, content_size, child.align)
                child.layout_pos(pos_x, pos_y)
            elif 2 == child.anchor: #锚定右边
                content_size = child.layout_size(div_size)
                right_x, pos_x, pos_y = _layout_horizontal_content(2,right_x, ref_y, ref_h, content_size, child.align)
                child.layout_pos(pos_x, pos_y)
            else:
                center_children.append(child)
        #锚定中间
        center_num = len(center_children)
        if center_num > 0:
            no_space = True
            remain_width = right_x - left_x
            center_content_width = sum([c.layout_size(div_size)[0] for c in center_children])
            interval = 0
            if remain_width > 0:
                no_space = False
                interval = float(remain_width - center_content_width) / 2.0
            left_x += interval
            for child in center_children: #布局锚定居中的content
                if no_space:
                    child.width = 0
                    child.height = 0
                else:
                    content_size = child.layout_size(div_size)
                    left_x, pos_x, pos_y = _layout_horizontal_content(1,left_x, ref_y, ref_h, content_size, child.align)
                    child.layout_pos(pos_x, pos_y)

def _doc_flow_contents_layout(x, y, size, padding, contents_children):
    div_size = size
    left_x = x + padding[0]
    right_x = x + size[0] - padding[1]
    ref_y = y + div_size[1] - padding[2]
    if contents_children:
        max_content_h = 0
        for child in contents_children:
            content_size = child.layout_size(div_size)
            content_w, content_h = content_size
            if left_x +  content_w > right_x:
                #换行
                ref_y -= max_content_h
                left_x = x + padding[0]
                max_content_h = 0
                if ref_y < y:
                    break
            max_content_h = max(max_content_h, content_h)
            left_x, pos_x, pos_y = _layout_horizontal_content(1,left_x, ref_y, 0, content_size, 2)
            child.layout_pos(pos_x, pos_y)

def _center_contents_layout(x, y, size, padding, contents_children):
    #所有内容对象等间隔排版
    div_size = size
    center_children = contents_children
    content_num = len(center_children)
    if content_num > 0:
        left_x = x + padding[0]
        ref_y = y + padding[3]
        ref_h = div_size[1] - padding[2] - padding[3]
        center_content_width = sum([c.layout_size(div_size)[0] for c in center_children])
        interval = float(div_size[0] - center_content_width) / (content_num + 1)
        left_x += interval
        for child in center_children:
            content_size = child.layout_size(div_size)
            left_x, pos_x, pos_y = _layout_horizontal_content(1,left_x, ref_y, ref_h, content_size, child.align)
            child.layout_pos(pos_x, pos_y)
            left_x += interval

def _float_contents_layout(x, y, size, padding, contents_children):
    #所有内容对象按自己的pos_hint来排版
    div_width, div_height = size
    c_x = x + padding[0]
    c_y = y + padding[3]
    for child in contents_children:
        content_size = child.layout_size(size)
        if child.center_hint is not None:
            c_width, c_height = content_size
            c_h_x, c_h_y = child.center_hint
            c_x = x + div_width*c_h_x - 0.5*c_width
            c_y = y + div_height*c_h_y - 0.5*c_height
        elif child.pos_hint is not None:
            c_h_x, c_h_y = child.pos_hint
            c_x = x + div_width*c_h_x
            c_y = y + div_height*c_h_y
        child.layout_pos(c_x, c_y)

def _horizontal_contents_layout(typesetting, x, y, size, padding, contents_children):
    #当div的方向是横向时,内容排版方式分为:按内容的停靠属性显示,直接居中显示 和浮动显示(按内容的pos_hit属性)
    if typesetting == 'anchor':
        _anchor_contents_layout(x, y, size, padding, contents_children)
    elif typesetting == 'center':
        _center_contents_layout(x, y, size, padding, contents_children)
    elif typesetting == 'float':
        _float_contents_layout(x, y, size, padding, contents_children)
    elif typesetting == 'doc_flow':
        _doc_flow_contents_layout(x, y, size, padding, contents_children)

def _division_compute_height(division, width):
    compute_h = 0
    orientation = division.orientation
    div_width = width
    children = division.division_children
    if len(children) > 0:
        if orientation == 'vertical':
            for child in children:
                child_h = _division_compute_height(child, div_width)
                compute_h += child_h
        else:
            weights = [c._weight for c in children if c.absolute_width is None]
            sum_w = sum(weights)
            real_width = div_width - sum([c.absolute_width for c in children if c.absolute_width is not None])
            i = 0
            for child in children:
                if child.absolute_width is not None:
                    w = child.absolute_width
                else:
                    w = real_width*(float(weights[i])/sum_w)
                    i += 1
                child.width = w
                child_h = _division_compute_height(child, w)
                compute_h = max(compute_h, child_h)
    content_h = division.max_content_height(div_width)
    return max(compute_h, content_h)


def _offset_horizontal_div(child, offset):
    child.y += offset
    descendants = child.division_children
    for descendant in descendants:
        _offset_horizontal_div(descendant, offset)

def _division_layout_wrap(division):
    '''
    当division的wrap_height_flag为true，division的高度正好包裹内容高度。
    此时division的size_hint被忽略，absolute_height仍然有效果
    '''
    compute_h = 0
    division._wrap_content_height = True
    orientation = division.orientation
    x = division.x
    y = division.y
    div_width = division.width
    children = division.division_children
    if len(children) > 0:
        if orientation == 'horizontal':
            weights = [c._weight for c in children if c.absolute_width is None]
            sum_w = sum(weights)
            real_width = div_width - sum([c.absolute_width for c in children if c.absolute_width is not None])
            i = 0
            last_child = None
            for child in children:
                if child.absolute_width is not None:
                    width = child.absolute_width
                else:
                    width = real_width*(float(weights[i])/sum_w)
                    i += 1
                child.width = width
                if last_child is None:
                    child.x = x
                else:
                    child.x = last_child.x + last_child.width
                last_child = child
                child.y = y
                child_h = _division_layout_wrap(child)
                compute_h = max(compute_h, child_h)
            #调整同一行不等高的div上对齐
            for child in children:
                offset = compute_h - child.height
                if offset > 0:
                    _offset_horizontal_div(child, offset)
        elif orientation == 'vertical':
            rvs_children = children[:]
            rvs_children.reverse()
            for child in rvs_children:
                child.width = div_width
                child.x = x
                child.y = y
                child_h = _division_layout_wrap(child)
                compute_h += child_h
                y += child_h
        else:
            for child in children:
                child.width = div_width
                child.x = x
                child.y = y
                child_h = _division_layout_wrap(child)
                compute_h = max(compute_h, child_h)

    if division.absolute_height is not None:
        div_h = division.absolute_height
    else:
        content_h = division.max_content_height(div_width)
        div_h = max(content_h, compute_h)
    division.height = div_h
    division.finish_layout()
    #print '---division_layout_wrap pos={},size={}'.format(division.pos, division.size)
    return div_h

def _division_layout(division):
    #print 'begin layout ={}'.format((x,y,width,height))
    orientation = division.orientation
    x = division.x
    y = division.y
    width = division.width
    height = division.height
    children = division.division_children
    if len(children) > 0:
        real_width = width
        if orientation == 'horizontal':
            weights = [c._weight for c in children if c.absolute_width is None]
            sum_w = sum(weights)
            real_height  = height
            real_width = width - sum([c.absolute_width for c in children if c.absolute_width is not None])
        elif orientation == 'vertical':
            temp_c =  [c for c in children if c._wrap_content_height]
            for c in temp_c:
                #c.absolute_height = c.max_content_height(width)
                c.absolute_height = _division_compute_height(c, width)
            weights = [c._weight for c in children if c.absolute_height is None]
            sum_w = sum(weights)
            real_width =width
            real_height =height - sum([c.absolute_height for c in children if c.absolute_height])
        i = 0
        last_child = None
        if orientation == 'horizontal':
            for child in children:
                child.height = real_height
                if child.absolute_width is not None:
                    child.width = child.absolute_width
                else:
                    child.width = real_width*(float(weights[i])/sum_w)
                    i += 1
                if last_child is None:
                    child.x = x
                else:
                    child.x = last_child.x +  last_child.width
                last_child = child
                child.y = y
                _division_layout(child)
                child.finish_layout()
        elif orientation == 'vertical':
            for child in children:
                child.width = real_width
                if child.absolute_height is not None:
                    child.height = child.absolute_height
                else:
                    child.height = real_height*(float(weights[i])/sum_w)
                    i += 1
                if last_child is None:
                    child.y = y + height - child.height
                else:
                    child.y = last_child.y -  child.height
                last_child = child
                child.x = x
                _division_layout(child)
                child.finish_layout()
        else:
            #所有子div重叠在一起
            for child in children:
                child.width = real_width
                child.height = height
                child.x = x
                child.y = y
                _division_layout(child)
                child.finish_layout()

def _appropriate_size(ref_size, expect_size, size_hint, aspect_ratio, max_width, min_width, max_height, min_height):
    w, h  =  ref_size[:]
    hint_w = None
    hint_h = None
    shape_w, shape_h = ref_size[:]
    if expect_size is not None:
        _w, _h = expect_size[:]
        if _w is not None:
            shape_w = _w
        if _h is not None:
            shape_h = _h
        if _w is None:
            if size_hint and size_hint[0] is not None:
                shape_w = size_hint[0]*w
            else:
                shape_w = _h*aspect_ratio
        if _h is None:
            if size_hint and size_hint[1] is not None:
                shape_h = size_hint[1]*h
            else:
                shape_h = _w / aspect_ratio
    else:
        if size_hint is not None:
            hint_w, hint_h = size_hint[:]
        if hint_w is not None:
            shape_w = hint_w*w
        if hint_h is not None:
            shape_h = hint_h*h
        if hint_w is None:
            shape_w = shape_h*aspect_ratio
        if hint_h is None:
            shape_h = shape_w / aspect_ratio
    if min_height is not None:
        if shape_h < min_height:
            shape_h = min_height
            shape_w = shape_h*aspect_ratio
    if min_width is not None:
        if shape_w < min_width:
            shape_w = min_width
            shape_h = shape_w / aspect_ratio
    if max_width is not None:
        if shape_w > max_width:
            shape_w = max_width
            shape_h = shape_w / aspect_ratio
    if max_height is not None:
        if shape_h > max_height:
            shape_h = max_height
            shape_w = shape_h*aspect_ratio
    return shape_w, shape_h

class Group(Content):
    def __init__(self, **kwargs):
        self.contents_children = []
        #组容器大小大小
        self._group_size = None

        #组容器宽高比
        self._aspect_ratio = 1.0

        #是否自适应内容大小
        self._auto_size_flag = True

        #内容对象是横向或垂直布局
        self._orientation = 'horizontal' # 'vertical'

        #内容对象呈现形式
        # center=内容等间隔居中,此时忽略内容的anchor属性
        # anchor=根据内容的anchor属性自动停靠
        # float=根据内容的pos_hint属性布局
        self._contents_typesetting = 'center' # 'anchor' 'float' 'doc_flow'

        #self.layout_contents = None

        #组里内容的大小，也就是group_size去掉margin后的大小
        self.group_content_size = (0,0)
        self.group_content_pos = (0,0)
        super(Group, self).__init__(**kwargs)


    def on_touch_down(self, touch):
        for content in self.contents_children:
            if content.on_touch_down(touch):
                return True
        return super(Group,self).on_touch_down(touch)

    def on_touch_up(self, touch):
        for content in self.contents_children:
            content.on_touch_up(touch)
        return super(Group, self).on_touch_up(touch)

    def get_host_division(self):
        if self.parent:
            return self.parent.get_host_division()
        return None

    def _layout_contents(self,*args):
        #由parent（DIV）的orientation决定
        padding = (0,0,0,0)
        if not self._auto_size_flag:
            _horizontal_contents_layout(self._contents_typesetting, self.group_content_pos[0], self.group_content_pos[1], self.group_content_size, padding, self.contents_children)
        else:
            #如果自适应大小，里面内容只能按居中排版
            _center_contents_layout(self.group_content_pos[0], self.group_content_pos[1], self.group_content_size, padding, self.contents_children)

    def notify_changed(self, immediate=False):
        if self.parent is None:
            return
        self.parent.notify_changed(immediate)

    def layout_size(self, ref_size):
        if self._is_relayout(ref_size):
            self.property_changed = True
        if not self.property_changed:
            #print 'group property not changed'
            return self._size
        margin = self._margin
        if not self._auto_size_flag:
            act_size  =  max(ref_size[0] - margin[0] - margin[1], 0), max(ref_size[1] - margin[2] - margin[3], 0)
            shape_w, shape_h = _appropriate_size(act_size, self._group_size, self._size_hint, self._aspect_ratio, self._max_width, self._min_width, self._max_height, self._min_height)
        else:
            shape_w, shape_h = 30, 20
            _orientation = 0 if self._orientation == 'horizontal' else 1
            #如果自适应大小，里面内容只能按居中排版
            contents_size = [content.layout_size((0,0)) for content in self.contents_children]
            if contents_size:
                if 0 == _orientation:
                    shape_w = sum([s[0] for s in contents_size])
                    shape_h = max([s[1] for s in contents_size])
                else:
                    shape_w = max([s[0] for s in contents_size])
                    shape_h = sum([s[1] for s in contents_size])
        self.property_changed = False
        self.group_content_size = (shape_w, shape_h)
        self._size = (shape_w + margin[0] + margin[1], shape_h + margin[2] + margin[3])
        return self._size

    def layout_pos(self,x, y):
        self._x = x
        self._y = y
        self.group_content_pos = (self._x + self._margin[0], self._y + self._margin[3])
        self._layout_contents()

    def do_apply(self):
        for content in self.contents_children:
            content.do_apply()

    def add_instruction(self):
        instruction_group = self._instruction_group
        self.push_state(instruction_group)
        for content in self.contents_children:
            content.add_instruction()
        self.pop_state(instruction_group)

    def add_widget(self, widget, index=0, canvas=None):
        if not isinstance(widget, Content):
            raise Exception('group widget only add content')
        if widget.parent:
            raise Exception('widget already has host parent')
        self.contents_children.append(widget)
        self._instruction_group.add(widget._instruction_group)
        widget.parent =self
        self.notify_property_changed()

    def remove_widget(self, widget):
        if not isinstance(widget, Content):
            raise Exception('group widget only remove content')
        if widget in self.contents_children:
            self.contents_children.remove(widget)
        self._instruction_group.remove(widget._instruction_group)
        widget.parent = None
        self.notify_property_changed()

    def clear_widgets(self, children=None):
        for child in self.contents_children[:]:
            self.remove_widget(child)

    @property
    def orientation(self):
        return self._orientation

    @orientation.setter
    def orientation(self, value):
        if self._orientation != value:
            self._orientation = value
            self.notify_property_changed()

    @property
    def contents_typesetting(self):
        return self._contents_typesetting

    @contents_typesetting.setter
    def contents_typesetting(self, value):
        self._contents_typesetting = value
        if self._contents_typesetting != value:
            self._contents_typesetting = value
            self.notify_changed()

    @property
    def auto_size(self):
        return self._auto_size_flag

    @auto_size.setter
    def auto_size(self, value):
        #是否自适应内容大小
        #如果是True那么group_size，size_hint, aspect_ratio被忽略,容器的大小就等于里面内容的大小
        if self._auto_size_flag != value:
            self._auto_size_flag = value
            self.notify_property_changed()


    @property
    def aspect_ratio(self):
        return self._aspect_ratio

    @aspect_ratio.setter
    def aspect_ratio(self, value):
        #组宽高比
        if self._aspect_ratio != value:
            self._aspect_ratio = value
            self.notify_property_changed()

    @property
    def size(self):
        return self._group_size

    @size.setter
    def size(self, value):
        if self._group_size != value:
            self._group_size = value
            self.notify_property_changed()

class LightWidget(WidgetBase):
    __metaclass__ = WidgetMetaclass
    __events__ = ('on_touch_down', 'on_touch_move', 'on_touch_up')
    x = NumericProperty(0)
    y = NumericProperty(0)
    width = NumericProperty(100)
    height = NumericProperty(100)
    id = ''
    cls = []
    def __init__(self,**kwargs):
        #self.id = ''
        #self.cls = []
        #父对象
        self.parent = None
        #所有拥有触摸事件对象子集列表
        self.children = []
        self._instruction_group = InstructionGroup(nocompiler=True)
        if kwargs.has_key('clear_color'):
            self._instruction_group.add(Color(1,1,1,1))
        super(LightWidget, self).__init__(**kwargs)
        # Assign the default context of the widget creation.
        if not hasattr(self, '_context'):
            self._context = get_current_context()

    @property
    def __self__(self):
        return self

    def get_root_window(self):
        if self.parent:
            return self.parent.get_root_window()

    def get_parent_window(self):
        if self.parent:
            return self.parent.get_parent_window()

    def get_composite_root(self):
        if self.parent is None:
            raise Exception('division has no parent')
        return self.parent.get_composite_root()

    def collide_point(self, x, y):
        return self.x <= x <= (self.x + self.width) and self.y <= y <= (self.y + self.height)

    def to_widget(self, x, y, relative=False):
        '''Convert the given coordinate from window to local widget
        coordinates. See :mod:`~kivy.uix.relativelayout` for details on the
        coordinate systems.
        '''
        if self.parent:
            x, y = self.parent.to_widget(x, y)
        return self.to_local(x, y, relative=relative)

    def to_window(self, x, y, initial=True, relative=False):
        if not initial:
            x, y = self.to_parent(x, y, relative=relative)
        if self.parent:
            return self.parent.to_window(x, y, initial=False,
                                         relative=relative)
        return (x, y)

    def to_parent(self, x, y, relative=False):
        if relative:
            return (x + self.x, y + self.y)
        return (x, y)

    def to_local(self, x, y, relative=False):
        if relative:
            return (x - self.x, y - self.y)
        return (x, y)

    def _apply_transform(self, m, pos=None):
        if self.parent:
            x, y = self.parent.to_widget(relative=True,
                                         *self.to_window(*(pos or self.pos)))
            m.translate(x, y, 0)
            m = self.parent._apply_transform(m) if self.parent else m
        return m

    def get_window_matrix(self, x=0, y=0):
        m = Matrix()
        m.translate(x, y, 0)
        m = self._apply_transform(m)
        return m

    def on_touch_down(self, touch):
        for child in self.children[:]:
            if child.dispatch('on_touch_down', touch):
                return True

    def on_touch_move(self, touch):
        for child in self.children[:]:
            if child.dispatch('on_touch_move', touch):
                return True

    def on_touch_up(self, touch):
        for child in self.children[:]:
            if child.dispatch('on_touch_up', touch):
                return True

    def add_content(self, content):
        pass

    def remove_content(self, content):
        pass

    def add_widget(self, widget, index=0, canvas=None):
        pass

    def remove_widget(self, widget):
        pass

    def notify_changed(self):
        pass

    def get_host_division(self):
        pass


class Context(LightWidget):
    def __init__(self, **kwargs):
        self.content = None
        super(Context, self).__init__(**kwargs)

    def get_host_division(self):
        if self.parent:
            return self.parent.get_host_division()
        return None

    def notify_changed(self,immediate=False):
        if self.parent is None:
            return
        self.parent.notify_changed(immediate)

    def refresh_pos_size(self):
        if self.content is not None:
            self.x = self.content._x
            self.y = self.content._y
            self.width, self.height = self.content._size[:]
            #print 'context pos={} size={}'.format((self.x,self.y), (self.width,self.height))

    def add_content(self, content):
        if self.parent:
            self.parent.add_content(content)

    def remove_content(self, content):
        if self.parent:
            self.parent.remove_content(content)

    def add_widget(self, widget, index=0, canvas=None):
        if isinstance(widget, Division):
            raise Exception('Context can not add division')
        if widget.parent:
            raise Exception('Context already has host parent')
        if isinstance(widget, Content):
            if self.content is not None:
                raise Exception('Context only add one content')
            self.content = widget
            self.add_content(widget)
        elif isinstance(widget, Context):
            self.children.append(widget)
        self._instruction_group.add(widget._instruction_group)
        widget.parent =self

    def remove_widget(self, widget):
        if widget in self.children:
            self.children.remove(widget)
        self.clear_content()
        self._instruction_group.remove(widget._instruction_group)
        widget.parent = None

    def clear_content(self):
        if self.content is None:
            for context in self.children:
                context.clear_content()
        else:
            self.remove_content(self.content)

class Division(LightWidget):

    def __init__(self, **kwargs):
        #子DIV布局方式
        # horizontal 子div横向分布
        # vertical 子div纵向分布
        # overlap 子div重叠分布
        self._orientation = 'horizontal' # 'vertical' , 'overlap'

        #内容对象排版形式
        # center: 内容等间隔居中,此时忽略内容的anchor属性
        # anchor: 根据内容的anchor属性自动停靠
        # float: 根据内容的pos_hint属性布局
        #doc_flow: 内容按文档流方式排版，从左到右（自动换行），从上到下。
        self._contents_typesetting = 'center' # 'anchor' 'float' 'doc_flow'

        #div对象子集列表
        self.division_children = []

        #所拥有的内容对象列表
        self.contents_children = []

        #所拥有的context对象列表
        self.context_children = []

        #内容对象在div之间的间隔
        self._padding = (0,0,0,0)

        #div是否按子集内容对象的高度,设为True div的高度等于max_content_height
        self._wrap_content_height = False

        #子集div在父集中占位的权重，权重越大div就越大
        self._weight = 1.0

        #div的绝对高度或宽度
        self._absolute_width = None
        self._absolute_height = None

        #DIV是否已经被layout，如果还没layout，那么trigger_layout_contents不会执行，因为那时候div的pos和size还未知。
        self._finish_layout_flg = False

        #如果设置为True,
        self._manual_show = False

        self._do_layout_flg = False

        #self._instruction_group = InstructionGroup(nocompiler=True)
        self.trigger_layout = Clock.create_trigger(self._layout,0)
        self.trigger_layout_contents = Clock.create_trigger(self._layout_contents, -1)
        super(Division,self).__init__(**kwargs)

    def on_touch_down(self, touch):
        for content in self.contents_children:
            if content.on_touch_down(touch):
                return True
        return super(Division,self).on_touch_down(touch)

    def on_touch_up(self, touch):
        for content in self.contents_children:
            content.on_touch_up(touch)
        return super(Division, self).on_touch_up(touch)

    def max_content_height(self, ref_width):
        content_height = 0
        if self.contents_children:
            if self._contents_typesetting == 'doc_flow':
                line_h = 0
                line_x = 0
                for cc in self.contents_children:
                    c_w, c_h = cc.layout_size((ref_width, 0))
                    if line_x + c_w > ref_width:
                        #换行
                        content_height += line_h
                        line_h = 0
                        line_x = 0
                    line_x += c_w
                    line_h = max(line_h, c_h)
                content_height += line_h
            else:
                content_height = max([cc.layout_size((ref_width, 0))[1] for cc in self.contents_children])
            #print '######_max_content_height_height------{}'.format((ref_width,height))
        return content_height

    def _layout(self, dt):
        if self._do_layout_flg:
            _division_layout(self)
            self.finish_layout()
            self._do_layout_flg = False

    def finish_layout(self):
        self._finish_layout_flg = True
        self.trigger_layout_contents()
        #self._layout_contents(0)

    def notify_changed(self, immediate=False):
        #由子对象通知重新排版
        if self._wrap_content_height and self._absolute_height is None:
            #如果DIV的高度是随content的高度变换而变化，那么content变化后要通知父DIV从新布局
            self.notify_relayout(immediate)
        else:
            #固定高度就直接layout contents
            if immediate:
                self._layout_contents(0)
            else:
                self.trigger_layout_contents()

    def _layout_contents(self, dt):
        if self._finish_layout_flg:
            #div的内容排版方式分为:按内容的停靠属性显示,直接居中显示 和浮动显示(按内容的pos_hit属性)
            _horizontal_contents_layout(self._contents_typesetting, self.x, self.y, self.size, self._padding, self.contents_children)
            self._refresh_context_pos_size()
            self._show_contents()

    #通知父DIV重新布局子集
    def notify_relayout(self, immediate=False):
        if self._wrap_content_height and self._absolute_height is None:
            parent = self.parent
            if parent:
                parent.notify_relayout(immediate)
                return
        if self._finish_layout_flg:
            self._do_layout_flg = True
            self.trigger_layout()

    def _refresh_context_pos_size(self):
        for context in self.context_children:
            context.refresh_pos_size()

    def manual_show_contents(self):
        pass

    def _show_contents(self):
        for child in self.contents_children:
            child.add_instruction()
            child.do_apply()

    def remove_content(self, content):
        if content in self.contents_children:
            self.contents_children.remove(content)

    def add_content(self, content):
        self.contents_children.append(content)

    def add_widget(self, widget, index=0, canvas=None):
        if widget.parent:
            raise Exception('widget already has host parent')
        if isinstance(widget, Division):
            self.division_children.append(widget)
            self.children.append(widget)
            if self._finish_layout_flg:
                self.notify_relayout()
        elif isinstance(widget, Content):
            self.add_content(widget)
            if self._finish_layout_flg:
                self.notify_changed()
        elif isinstance(widget, Context):
            self.context_children.append(widget)
            self.children.append(widget)
            if widget.content is not None:
                self.add_content(widget.content)
            if self._finish_layout_flg:
                self.notify_changed()
        self._instruction_group.add(widget._instruction_group)
        widget.parent =self


    def remove_widget(self, widget):
        if widget in self.children:
            self.children.remove(widget)
        if isinstance(widget, Division):
            if widget in self.division_children:
                self.division_children.remove(widget)
            if self._finish_layout_flg:
                self.notify_relayout()
        elif isinstance(widget, Content):
            self.remove_content(widget)
            if isinstance(widget, Group):
                widget.clear_widgets()
            if self._finish_layout_flg:
                self.notify_changed()
        elif isinstance(widget, Context):
            if widget in self.context_children:
                self.context_children.remove(widget)
            widget.clear_content()
            if self._finish_layout_flg:
                self.notify_changed()
        self._instruction_group.remove(widget._instruction_group)
        widget.parent = None

    def clear_contents(self):
        contents = self.contents_children[:]
        for content in contents:
            self.remove_widget(content)

    def clear_widgets(self, children=None):
        divisions = self.division_children[:]
        for division in divisions:
            self.remove_widget(division)

    def get_host_division(self):
        return self

    @property
    def pos(self):
        return (self.x, self.y)
    @property
    def size(self):
        return (self.width, self.height)

    @property
    def orientation(self):
        return self._orientation

    @orientation.setter
    def orientation(self, value):
        self._orientation = value

    @property
    def wrap_content_height(self):
        return self._wrap_content_height

    @wrap_content_height.setter
    def wrap_content_height(self, value):
        self._wrap_content_height = value
        for content in self.contents_children:
            content._be_wrap = value

    @property
    def weight(self):
        return self._weight

    @weight.setter
    def weight(self, value):
        self._weight = value

    @property
    def contents_typesetting(self):
        return self._contents_typesetting

    @contents_typesetting.setter
    def contents_typesetting(self, value):
        self._contents_typesetting = value

    @property
    def absolute_width(self):
        return self._absolute_width

    @absolute_width.setter
    def absolute_width(self, value):
        self._absolute_width = value

    @property
    def absolute_height(self):
        return self._absolute_height

    @absolute_height.setter
    def absolute_height(self, value):
        self._absolute_height = value

    @property
    def padding(self):
        return self._padding

    @padding.setter
    def padding(self, value):
        self._padding = value

class Composite(Widget):
    orientation = OptionProperty('horizontal', options=( 'horizontal', 'vertical', 'overlap'))
    def __init__(self,**kwargs):
        self._do_layout_flg= False
        self._wrap_content_height = False
        self.full_division = None
        self.full_division = Division(clear_color=True)
        self.full_division.parent = self
        self.children.append(self.full_division)
        self.ids_animate = None
        self._animating_list = []
        self._step = 1.0 / 60.0
        self.trigger_layout = Clock.create_trigger(self.layout)
        super(Composite, self).__init__(**kwargs)
        self.canvas.add(self.full_division._instruction_group)

    def layout(self, dt, ref_size=None):
        full_division = self.full_division
        if ref_size is None:
            width = self.width
            height = self.height
        else:
            width,height = ref_size[:]
            self._do_layout_flg = True
        full_division.width = width
        if self._do_layout_flg:
            if self._wrap_content_height:
                self.height = _division_layout_wrap(full_division)
            else:
                full_division.height = height
                _division_layout(full_division)
                full_division.finish_layout()
            self._do_layout_flg = False

    def bind_content_id(self, content, content_id):
        #对内容运行动画时，一定要使用内容的id，所以如果在kv中没有绑定id，也可以调用该接口绑定
        if self.ids.has_key(content_id):
            if content.proxy_ref is not self.ids[content_id]:
                raise  Exception('id () has already bound by other content'.format(content_id))
            return
        self.ids[content_id] = content.proxy_ref

    def bind_animate(self, content_id, name, duration=None, delay=None):
        '''把内容与动画名绑定，重复绑定将覆盖前者
        content_ids： 内容的ids
        '''
        if self.ids_animate is None:
            self.ids_animate = {}
        if not self.ids.has_key(content_id):
            raise Exception('widget not find content_ids:{}'.format(content_id))
        content = self.ids[content_id]
        animate = self.ids_animate[content_id] = animate_factory(name)
        animate.bind(content, duration, delay)

    def _update_animate(self,dt):
        animate_list = self._animating_list
        for animate in animate_list[:]:
            if animate.update(dt):
                animate_list.remove(animate)
        if not animate_list:
            Clock.unschedule(self._update_animate)
            return True

    def start_animate(self, content_id, repeat=1):
        if not self.ids_animate.has_key(content_id):
            raise Exception('content_id:{} not bind '.format(content_id))
        animate = self.ids_animate[content_id]
        animate.start(repeat)
        animate_list = self._animating_list
        if not animate_list:
            Clock.schedule_interval(self._update_animate, self._step)
        if animate not in animate_list:
            animate_list.append(animate)

    def stop_animate(self, content_id):
        if not self.ids_animate.has_key(content_id):
            raise Exception('content_id:{} not bind '.format(content_id))
        animate = self.ids_animate[content_id]
        animate_list = self._animating_list
        if animate in animate_list:
            animate_list.remove(animate)
            animate.stop()
        if not animate_list:
            Clock.unschedule(self._update_animate)

    def stop_animate_all(self):
        for animate in self._animating_list:
            animate.stop()
        self._animating_list = []
        Clock.unschedule(self._update_animate)

    def get_composite_root(self):
        return self

    @property
    def contents_typesetting(self):
        if self.full_division:
            return self.full_division._contents_typesetting
        return ''

    @contents_typesetting.setter
    def contents_typesetting(self, value):
        self.full_division._contents_typesetting = value

    def on_orientation(self, instance, value):
        self.full_division._orientation = value

    def on_pos(self, instance, value):
        #print 'on_pos={}'.format(value)
        full_division = self.full_division
        if full_division:
            full_division.x = value[0]
            full_division.y = value[1]
            self._do_layout_flg = True
            self.trigger_layout()


    def on_width(self,instance, value):
        #print 'on_width={}'.format(value)
        full_division = self.full_division
        if full_division:
            if full_division.width != value:
                self._do_layout_flg = True
                self.trigger_layout()

    def on_height(self,instance, value):
        full_division = self.full_division
        if full_division:
            if not self._wrap_content_height:
                if full_division.height != value:
                    self._do_layout_flg = True
                    self.trigger_layout()

    def add_widget(self, widget, index=0, canvas=None):
        if self.full_division is None:
            raise Exception('CompositeWidget Division  can be created')
        self.full_division.add_widget(widget, index, canvas)

    def remove_widget(self, widget):
        if self.full_division is None:
            raise Exception('CompositeWidget Division  can be created')
        self.full_division.remove_widget(widget)

Builder.load_string('''
<RelativeComposite>
    canvas.before:
        PushMatrix
        Translate:
            xy: self.pos
    canvas.after:
        PopMatrix
''')

class RelativeComposite(Composite):

    @property
    def wrap_content_height(self):
        return self._wrap_content_height

    @wrap_content_height.setter
    def wrap_content_height(self, value):
        self._wrap_content_height = value

    def notify_relayout(self, immediate=False):
        self._do_layout_flg = True
        if immediate:
            self.layout(0)
        else:
            self.trigger_layout()

    def on_pos(self, instance, value):
        #print 'on_pos={}'.format(value)
        pass

    def to_parent(self, x, y, **k):
        return (x + self.x, y + self.y)

    def to_local(self, x, y, **k):
        return (x - self.x, y - self.y)

    def _apply_transform(self, m, pos=None):
        m.translate(self.x, self.y, 0)
        return super(RelativeComposite, self)._apply_transform(m, (0, 0))

    def on_touch_down(self, touch):
        touch.push()
        touch.apply_transform_2d(self.to_local)
        ret = super(RelativeComposite, self).on_touch_down(touch)
        touch.pop()
        return ret

    def on_touch_move(self, touch):
        touch.push()
        touch.apply_transform_2d(self.to_local)
        ret = super(RelativeComposite, self).on_touch_move(touch)
        touch.pop()
        return ret

    def on_touch_up(self, touch):
        touch.push()
        touch.apply_transform_2d(self.to_local)
        ret = super(RelativeComposite, self).on_touch_up(touch)
        touch.pop()
        return ret